#pragma once

//实现文件存储子服务
//1. 实现文件RPC服务类---实现rpc调用的业务处理接口

//2. 实现文件存储子服务的服务器类

//3. 实现文件存储子服务类的构造者

#include <gflags/gflags.h>
#include <brpc/server.h>
#include <butil/logging.h>

#include "etcd.hpp"
#include "logger.hpp"
#include "utils.hpp"
#include "file.pb.h"
#include "base.pb.h"


namespace ChatSystem
{
    // 1. 创建rpc服务子类继承pb(protobuf)中的FileService服务类，并实现内部的业务接口逻辑；
    class FileServiceImpl : public ChatSystem::FileService
    {
    public:
        FileServiceImpl(const std::string& storage_path)
        :_storage_path(storage_path)
        {
            //如果设置的存储路径 _storage_path 不存在就创建
            umask(0);
            mkdir(_storage_path.c_str(), 0775);
            if(_storage_path.back() != '/')
            {
                _storage_path += '/';
            }
        }
        ~FileServiceImpl(){}
        //单文件下载的接口
        void GetSingleFile(google::protobuf::RpcController* controller,
            const ::ChatSystem::GetSingleFileReq* request,
            ChatSystem::GetSingleFileRsp* response,
            google::protobuf::Closure* done) override
            {
                //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
                brpc::ClosureGuard rpc_gurd(done);
                response->set_request_id(request->request_id());  //设置响应的请求ID
                //1. 取出请求中的文件ID(其实就是文件名)
                std::string fid = request->file_id();
                std::string filename = _storage_path + fid;
                //2. 将文件ID作为文件名，读取文件数据
                std::string file_body;
                bool ret = ChatSystem::Util::readFile(filename, file_body);
                if(ret == false)
                {
                    //读取文件数据失败后也要组织响应
                    response->set_success(false);
                    response->set_errmsg("读取文件数据失败！");
                    LOG_ERROR("单文件下载请求 {} , 从文件中读取数据时失败！", request->request_id());
                    return;
                }
                //3. 组织响应
                response->set_success(true);
                //mutable_file_data() 是获取file_data()对象的指针，通过指针来设置file_data的值；
                response->mutable_file_data()->set_file_id(fid);
                response->mutable_file_data()->set_file_content(file_body);
            }

        //多文件的下载接口
        void GetMultiFile(google::protobuf::RpcController* controller,
            const ::ChatSystem::GetMultiFileReq* request,
            ChatSystem::GetMultiFileRsp* response,
            google::protobuf::Closure* done) override
        {
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            response->set_request_id(request->request_id());  //设置响应的请求ID

            //循环取出请求中的文件ID，读取文件数据进行填充就可以了
            for(int i = 0; i < request->file_id_list_size(); ++i)
            {
                //1. 依次获取文件ID列表中的每个文件ID
                std::string fid = request->file_id_list(i);
                std::string filename = _storage_path + fid;
                //2. 将文件ID作为文件名，读取文件数据
                std::string file_body;
                bool ret = ChatSystem::Util::readFile(filename, file_body);
                //批量获取文件数据的时候，只要有一个文件数据读取失败了就直接失败，因为批量文件获取的请求是一个整体，不能以单个文件读取是否成功来评判
                if(ret == false)
                {
                    //读取文件数据失败后也要组织响应
                    response->set_success(false);
                    response->set_errmsg("读取文件数据失败！");
                    LOG_ERROR("批量下载请求 {} , 从文件中读取数据时失败！", request->request_id());
                    return;
                }
                //3. 依次往响应中设置文件数据
                FileDownloadData data;
                data.set_file_id(fid);
                data.set_file_content(file_body);
                response->mutable_file_data()->insert({fid, data});
            }
            response->set_success(true);
        }

        //单文件的上传接口
        void PutSingleFile(google::protobuf::RpcController* controller,
            const ::ChatSystem::PutSingleFileReq* request,
            ChatSystem::PutSingleFileRsp* response,
            google::protobuf::Closure* done) override
        {
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            response->set_request_id(response->request_id());

            //1. 为文件生成一个唯一的UUID作为文件名以及文件ID
            std::string fid = ChatSystem::Util::uuid();
            std::string filename = _storage_path + fid;

            //2. 取出请求中的文件数据，进行文件数据写入
            bool ret = ChatSystem::Util::writeFile(filename, request->file_data().file_content());
            if(ret == false)
            {
                response->set_success(false);
                response->set_errmsg("文件上传失败！");
                LOG_ERROR("单文件上传请求 {} , 往文件中写入数据时失败！", request->request_id());
                return;
            }
            //组织响应
            response->set_success(true);
            auto file_info_ptr = response->mutable_file_info();
            file_info_ptr->set_file_id(fid);
            file_info_ptr->set_file_size(request->file_data().file_size());
            file_info_ptr->set_file_name(request->file_data().file_name());
        }

        //多文件的上传接口
        void PutMultiFile(google::protobuf::RpcController* controller,
            const ::ChatSystem::PutMultiFileReq* request,
            ChatSystem::PutMultiFileRsp* response,
            google::protobuf::Closure* done) override
        {
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            response->set_request_id(response->request_id());

            //循环取出请求中的文件数据，依次为每个文件生成唯一的UUID后，并进行文件写入
            for(int i = 0; i < request->file_data_size(); ++i)
            {
                //1. 为文件生成一个唯一的UUID作为文件名以及文件ID
                std::string fid = ChatSystem::Util::uuid();
                std::string filename = _storage_path + fid;

                //2. 取出请求中的文件数据，进行文件数据写入
                bool ret = ChatSystem::Util::writeFile(filename,  request->file_data(i).file_content());
                if(ret == false)
                {
                    response->set_success(false);
                    response->set_errmsg("文件上传失败！");
                    LOG_ERROR("批量文件上传请求 {} , 往文件中写入数据时失败！", request->request_id());
                    return;
                }
                //组织响应
                response->set_success(true);
                ChatSystem::FileMessageInfo * info = response->add_file_info();
                info->set_file_id(fid);
                info->set_file_size(request->file_data(i).file_size());
                info->set_file_name(request->file_data(i).file_name());
            }
        }
    private:
        std::string _storage_path;  //设置上传的文件的存储路径
    };


    //文件存储子服务器搭建的流程类
    class FileServer
    {
    public:
        using ptr = std::shared_ptr<FileServer>;
        FileServer(const Registry::ptr& reg_client, const std::shared_ptr<brpc::Server>& rpc_server)
        :_reg_client(reg_client), _rpc_server(rpc_server)
        {}
        ~FileServer(){}

        //搭建RPC服务器，并启动服务器
        /* make_reg_object 接口的调用必须在start接口调用之前，
        因为start涉及到了启动服务器，而启动服务器的前提是客户端对象和服务注册客户端对象已经实例化好了; 
        为了防止使用者在start接口调用之前未调用 make_reg_object 接口而导致
        服务器无法启动的问题，我们可以用建造者模式，将构造过程进行控制，在代码中规定调用顺序，去倒逼使用者;
        使用建造者模式的原因还是由于构造FileServer对象时参数太多，为了简化FileServer对象的构造 */
        void start()
        {
            // 阻塞等待服务端运行结束
            _rpc_server->RunUntilAskedToQuit();
        }
    private:
        Registry::ptr _reg_client;  //注册中心客户端
        std::shared_ptr<brpc::Server> _rpc_server;   //brpc服务器
    };

    //建造者模式---用来构造FileServer对象，将FileServer对象的真正构造过程给封装起来，通过封装的类去构造 FileServer对象
    class FileServerBuilder
    {
    public:

        //用于构造服务注册客户端对象
        void make_reg_object(const std::string& reg_host,  //注册中心的地址
            const std::string& service_name,    //服务名称
            const std::string& access_host) //提供服务器的主机的地址信息
        {
            _reg_client = std::make_shared<Registry>(reg_host);
            _reg_client->registry(service_name, access_host);
        }

        //用于构建RPC服务器
        void make_rpc_server(uint16_t port, 
            uint32_t timeout,      //连接空闲超时时间---超时后连接关闭
            uint8_t num_threads, 
            const std::string& path = "./data")    //rpc服务器的线程数量---IO线程数量；
        {
            _rpc_server = std::make_shared<brpc::Server>();

            // 向rpc服务器类中添加rpc子服务对象---告诉服务器收到什么请求用哪个接口处理；
            /*注意：如果在file_service对象是栈上创建的，AddService的第一个参数就是file_service对象的地址，当
            make_rpc_server函数调用结束后file_service这个对象是被销毁了的，而在 _rpc_server中会使用到 file_service对象
            此时就会出现段错误，所以我们这里需要在堆上创建对象，这样make_rpc_server函数调用结束后再使用file_service对象也不会错。*/
            FileServiceImpl* file_service = new FileServiceImpl(path);
            /* SERVER_DOESNT_OWN_SERVICE 表示添加服务失败时，服务器也不会删除服务器对象； SERVER_OWNS_SERVICE 表示添加服务失败时，
            服务器将负责删除服务对象，当我们在堆上创建file_service对象时需要使用SERVER_OWNS_SERVICE，就不会造成内存泄漏。 */
            int ret = _rpc_server->AddService(file_service,
                brpc::ServiceOwnership::SERVER_OWNS_SERVICE); 
            if(ret == -1)
            {
                LOG_ERROR("添加Rpc服务失败！ ");
                abort();
            }
            brpc::ServerOptions options;
            options.idle_timeout_sec - timeout;
            options.num_threads = num_threads;
            ret = _rpc_server->Start(port, &options);   //这个是一个阻塞接口
            if(ret == -1)
            {
                LOG_ERROR("服务器启动失败！");
                abort();
            }
        }

        //构造一个 FileServer对象
        FileServer::ptr build()
        {
            if(!_reg_client)
            {
                LOG_ERROR("还未初始化服务注册模块");
                abort();
            }
            if(!_rpc_server)
            {
                LOG_ERROR("还未初始化RPC服务器模块");
                abort();
            }
            FileServer::ptr server = std::make_shared<FileServer>(_reg_client, _rpc_server);
            return server;
        }
    private:
        Registry::ptr _reg_client;  //注册中心客户端
        std::shared_ptr<brpc::Server> _rpc_server;   //brpc服务器
    };


}